/**
 * OWASP Enterprise Security API (ESAPI)
 *
 * This file is part of the Open Web Application Security Project (OWASP)
 * Enterprise Security API (ESAPI) project. For details, please see
 * <a href="http://www.owasp.org/index.php/ESAPI">http://www.owasp.org/index.php/ESAPI</a>.
 *
 * Copyright (c) 2007-2019 - The OWASP Foundation
 *
 * The ESAPI is published by OWASP under the BSD license. You should read and accept the
 * LICENSE before you use, modify, and/or redistribute this software.
 *
 * @author Jeff Williams <a href="http://www.aspectsecurity.com">Aspect Security</a>
 * @created 2009
 */
package org.owasp.esapi;

import java.util.ArrayList;
import org.owasp.esapi.codecs.Codec;

/**
 * A parameterized string that uses escaping to make untrusted data safe before combining it with
 * a command or query intended for use in an interpreter.
 * <pre>
 * PreparedString div = new PreparedString( "&lt;a href=\"http:\\\\example.com?id=?\" onmouseover=\"alert('?')\"&gt;test&lt;/a&gt;", new HTMLEntityCodec() );
 * div.setURL( 1, request.getParameter( "url" ), new PercentCodec() );
 * div.set( 2, request.getParameter( "message" ), new JavaScriptCodec() );
 * out.println( div.toString() );
 *
 * // escaping for SQL
 * PreparedString query = new PreparedString( "SELECT * FROM users WHERE name='?' AND password='?'", new OracleCodec() );
 * query.set( 1, request.getParameter( "name" ) );
 * query.set( 2, request.getParameter( "pass" ) );
 * stmt.execute( query.toString() );
 * </pre>
 *
 * @author Jeff Williams (jeff.williams .at. aspectsecurity.com) <a href="http://www.aspectsecurity.com">Aspect Security</a>
 * @since June 1, 2007
 */
public class PreparedString {
    char parameterCharacter = '?';
    Codec codec = null;
    String[] parameters = null;
    ArrayList parts = new ArrayList();
    private final static char[] IMMUNE = {};

    /**
     * Create a PreparedString with the supplied template and Codec. The template should use the
     * default parameter placeholder character (?) in the place where actual parameters are to be inserted.
     * The supplied Codec will be used to escape characters in calls to set, unless a specific Codec is
     * provided to override it.
     * @param template
     * @param codec
     */
    public PreparedString( String template, Codec codec ) {
        this.codec = codec;
        split( template, parameterCharacter );
    }

    /**
     * Create a PreparedString with the supplied template, parameter placeholder character, and Codec. The parameter character
     * can be any character, but should not be one that will be used in the template. The parameter character can safely
     * be used in a parameter passed into the set methods.
     * @param template
     * @param parameterCharacter
     * @param codec
     */
    public PreparedString( String template, char parameterCharacter, Codec codec ) {
        this.codec = codec;
        this.parameterCharacter = parameterCharacter;
        split( template, parameterCharacter );
    }

    /**
     * Split a string with a particular character.
     * @param str
     * @param c
     */
    private void split( String str, char c ) {
        int index = 0;
        int pcount = 0;
        for ( int i = 0; i < str.length(); i++ ) {
            if ( str.charAt(i) == c ) {
                pcount++;
                parts.add( str.substring(index,i) );
                index = i + 1;
            }
        }
        parts.add( str.substring(index) );
        parameters = new String[pcount];
    }

    /**
     * Set the parameter at index with supplied value using the default Codec to escape.
     * @param index
     * @param value
     */
    public void set( int index, String value ) {
        if ( index < 1 || index > parameters.length ) {
            throw new IllegalArgumentException( "Attempt to set parameter " + index + " on a PreparedString with only " + parameters.length + " placeholders" );
        }
        String encoded = codec.encode( IMMUNE, value );
        parameters[index-1] = encoded;
    }

    /**
     * Set the parameter at index with supplied value using the supplied Codec to escape.
     * @param index
     * @param value
     * @param codec
     */
    public void set( int index, String value, Codec codec ) {
        if ( index < 1 || index > parameters.length ) {
            throw new IllegalArgumentException( "Attempt to set parameter " + index + " on a PreparedString with only " + parameters.length + " placeholders" );
        }
        String encoded = codec.encode( IMMUNE, value );
        parameters[index-1] = encoded;
    }

    /**
     * Render the PreparedString by combining the template with properly escaped parameters.
     */
    public String toString() {
        for ( int ix = 0; ix < parameters.length; ix++ ) {
            if ( parameters[ix] == null ) {
                throw new RuntimeException( "Attempt to render PreparedString without setting parameter " + ( ix + 1 ));
            }
        }
        StringBuilder sb = new StringBuilder();
        int i = 0;
        for ( int p=0; p < parts.size(); p++ ) {
            sb.append( parts.get( p ) );
            if ( i < parameters.length ) sb.append( parameters[i++] );
        }
        return sb.toString();
    }
}
